﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Net.Sockets;
using System.Net;

namespace LiveCodeServer
{
    /// <summary>
    /// Class to hold an open client connection
    /// </summary>
    public class Client
    {
        #region Class Variables
        private ClientHandler clientHandler;

        private bool bUserLoggedOn = false;

        /// <summary>
        /// User associated with the client once they've logged on
        /// </summary>
        private User clientUser;

        #region I/O Variables
        private byte[] bytDefaultDataBuffer = new byte[2048];
        private Socket socDefaultClientConnection;

        private List<byte[]> lstQuickRecieveDataBuffers = new List<byte[]>();
        private List<byte[]> lstSlowRecieveDataBuffers = new List<byte[]>();

        private List<int> lstQuickSendPorts = new List<int>();
        private List<int> lstSlowSendPorts = new List<int>();

        private List<Socket> lstQuickSendSockets = new List<Socket>();
        private int iQuickSendIndex = 0;
        private List<Socket> lstSlowSendSockets = new List<Socket>();
        private int iSlowSendIndex = 0;

        private List<Socket> lstQuickRecieveSockets = new List<Socket>();
        private List<Socket> lstSlowRecieveSockets = new List<Socket>();
        #endregion
        #endregion

        public ClientHandler ClientHandler
        {
            get
            {
                return this.clientHandler;
            }
        }

        /// <summary>
        /// Checks that socket is connected,
        /// sends a confirmation of the connection,
        /// and waits for data from client
        /// </summary>
        /// <param name="socClient"></param>
        public Client(Socket socClient, ClientHandler _clientHandler)
        {
            this.socDefaultClientConnection = socClient;
            this.clientHandler = _clientHandler;
            this.WaitForSlowData(new SocketInfo(socClient, SocketInfo.SocketTypes.Default, -1));
        }

        /// <summary>
        /// Closes all socket connections from the client
        /// </summary>
        public void DisconnectFromClient()
        {
            if (this.bUserLoggedOn)
            {
                this.LogUserOff();
            }

            this.bUserLoggedOn = false;
            this.clientHandler.UserLoggedOut(this.clientUser);

            foreach (Socket soc in this.lstQuickRecieveSockets)
            {
                if (soc.Connected)
                {
                    soc.Shutdown(SocketShutdown.Both);
                    soc.Close();
                }
            }

            foreach (Socket soc in this.lstSlowRecieveSockets)
            {
                if (soc.Connected)
                {
                    soc.Shutdown(SocketShutdown.Both);
                    soc.Close();
                }
            }

            foreach (Socket soc in this.lstQuickSendSockets)
            {
                if (soc.Connected)
                {
                    soc.Shutdown(SocketShutdown.Both);
                    soc.Close();
                }
            }

            foreach (Socket soc in this.lstSlowSendSockets)
            {
                if (soc.Connected)
                {
                    soc.Shutdown(SocketShutdown.Both);
                    soc.Close();
                }
            }
        }

        /// <summary>
        /// Sends a logoff message to the client
        /// </summary>
        private void LogUserOff()
        {
            XmlDocument xmlLogout = new XmlDocument();
            XmlNode xmlLiveCode = xmlLogout.AppendChild(xmlLogout.CreateElement("LiveCode"));
            XmlNode xmlLogoutNode = xmlLiveCode.AppendChild(xmlLogout.CreateElement("Logout"));

            this.SendSlowData(xmlLogout);
        }

        public void SendChatMessage(int iToID, int iFromID, string sText)
        {
            //Make sure this is a message to the correct user
            if (iToID == this.clientUser.ID)
            {
                //Create the xml to send
                XmlDocument xmlChatMessage = new XmlDocument();
                XmlNode xmlLiveCode = xmlChatMessage.AppendChild(xmlChatMessage.CreateElement("LiveCode"));
                XmlNode xmlChat = xmlLiveCode.AppendChild(xmlChatMessage.CreateElement("Chat"));
                XmlNode xmlMessage = xmlChat.AppendChild(xmlChatMessage.CreateElement("Message"));
                XmlAttribute xmlToID = xmlMessage.Attributes.Append(xmlChatMessage.CreateAttribute("toID"));
                xmlToID.Value = iToID.ToString();
                XmlAttribute xmlFromID = xmlMessage.Attributes.Append(xmlChatMessage.CreateAttribute("fromID"));
                xmlFromID.Value = iFromID.ToString();
                XmlAttribute xmlText = xmlMessage.Attributes.Append(xmlChatMessage.CreateAttribute("text"));
                xmlText.Value = sText;

                this.SendSlowData(xmlChatMessage);
            }
        }

        public void SendChatUserLogon(int iID, string sName)
        {
            //Create the xml to send
            XmlDocument xmlChatLogon = new XmlDocument();
            XmlNode xmlLiveCode = xmlChatLogon.AppendChild(xmlChatLogon.CreateElement("LiveCode"));
            XmlNode xmlChat = xmlLiveCode.AppendChild(xmlChatLogon.CreateElement("Chat"));
            XmlNode xmlUserLogon = xmlChat.AppendChild(xmlChatLogon.CreateElement("UserLogon"));
            XmlAttribute xmlID = xmlUserLogon.Attributes.Append(xmlChatLogon.CreateAttribute("id"));
            xmlID.Value = iID.ToString();
            XmlAttribute xmlName = xmlUserLogon.Attributes.Append(xmlChatLogon.CreateAttribute("name"));
            xmlName.Value = sName;

            this.SendSlowData(xmlChatLogon);
        }

        public void SendChatUserLogoff(int iID)
        {
            //Create the xml to send
            XmlDocument xmlChatLogoff = new XmlDocument();
            XmlNode xmlLiveCode = xmlChatLogoff.AppendChild(xmlChatLogoff.CreateElement("LiveCode"));
            XmlNode xmlChat = xmlLiveCode.AppendChild(xmlChatLogoff.CreateElement("Chat"));
            XmlNode xmlUserLogoff = xmlChat.AppendChild(xmlChatLogoff.CreateElement("UserLogoff"));
            XmlAttribute xmlID = xmlUserLogoff.Attributes.Append(xmlChatLogoff.CreateAttribute("id"));
            xmlID.Value = iID.ToString();

            this.SendSlowData(xmlChatLogoff);
        }

        #region Wait for data
        private void WaitForQuickData(SocketInfo socketInfo)
        {
            try
            {
                socketInfo.ConnectionSocket.BeginReceive(this.bytDefaultDataBuffer, 0, this.bytDefaultDataBuffer.Length, SocketFlags.None, new AsyncCallback(OnQuickDataRecieved), socketInfo);
            }
            catch (SocketException e)
            {
                //TODO handle better

                string message = "Method: Client.WaitForQuickData";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }
        
        private void WaitForSlowData(SocketInfo socketInfo)
        {
            try
            {
                socketInfo.ConnectionSocket.BeginReceive(this.bytDefaultDataBuffer, 0, this.bytDefaultDataBuffer.Length, SocketFlags.None, new AsyncCallback(OnSlowDataRecieved), socketInfo);
            }
            catch (SocketException e)
            {
                //TODO handle better

                string message = "Method: Client.WaitForSlowData";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }
        #endregion

        #region Send Data
        /// <summary>
        /// Sends data on the default connection, should not be used after client is completely connected
        /// </summary>
        /// <param name="xmlDoc"></param>
        private void SendData(XmlDocument xmlDoc)
        {
            string sInputXMLDoc = xmlDoc.InnerXml;
            byte[] bytData = System.Text.Encoding.ASCII.GetBytes(sInputXMLDoc);

            try
            {
                this.socDefaultClientConnection.Send(bytData);
            }
            catch (SocketException socExp)
            {
                //TODO handle better

                string message = "Method: Client.SendData";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + socExp.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        private void SendQuickData(XmlDocument xmlDoc)
        {
            string sInputXMLDoc = xmlDoc.InnerXml;
            byte[] bytData = System.Text.Encoding.ASCII.GetBytes(sInputXMLDoc);

            try
            {
                if (this.iQuickSendIndex == this.lstQuickSendSockets.Count - 1)
                {
                    this.iQuickSendIndex = 0;
                }
                else
                {
                    this.iQuickSendIndex++;
                }

                this.lstQuickSendSockets[this.iQuickSendIndex].Send(bytData);
            }
            catch (SocketException socExp)
            {
                //TODO handle better

                string message = "Method: Client.SendQuickData";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + socExp.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        private void SendSlowData(XmlDocument xmlDoc)
        {
            string sInputXMLDoc = xmlDoc.InnerXml;
            byte[] bytData = System.Text.Encoding.ASCII.GetBytes(sInputXMLDoc);

            try
            {
                if (this.iSlowSendIndex == this.lstSlowSendSockets.Count - 1)
                {
                    this.iSlowSendIndex = 0;
                }
                else
                {
                    this.iSlowSendIndex++;
                }

                this.lstSlowSendSockets[this.iSlowSendIndex].Send(bytData);
            }
            catch (SocketException socExp)
            {
                //TODO handle better

                string message = "Method: Client.SendSlowData";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + socExp.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }
        #endregion

        #region Process Data
        private void QuickProcessRecievedXML(XmlDocument xmlDoc)
        {
            try
            {
                //If a text changed event
                if (xmlDoc.SelectSingleNode("/LiveCode/Text") != null)
                {
                    this.ProcessTextChanged(xmlDoc);
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.QuickProcessRecievedXML";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        private void SlowProcessRecievedXML(XmlDocument xmlDoc)
        {
            try
            {
                if (xmlDoc.SelectSingleNode("/LiveCode/Logout") != null)
                {
                    //If user is disconnecting, then make sure to clean up connections
                    this.DisconnectFromClient();
                }
                //If the xml document is a login request
                else if (xmlDoc.SelectSingleNode("/LiveCode/Login") != null)
                {
                    this.ClientLogin(xmlDoc);
                    if (this.clientUser != null)
                    {
                        this.bUserLoggedOn = true;

                        this.clientHandler.UserLoggedIn(this.clientUser);
                    }
                }
                //If the client is ready for the server to connect on the client's recieve ports
                else if (xmlDoc.SelectSingleNode("/LiveCode/Ports/ReadyForConnect") != null)
                {
                    this.ConnectToClientQuickSend();
                    this.ConnectToClientSlowSend();
                }
                //If client is logging onto a project
                else if (xmlDoc.SelectSingleNode("/LiveCode/ProjectLogin") != null)
                {
                    this.ProjectLogin(xmlDoc);
                }
                //If client sent a instant message
                else if (xmlDoc.SelectSingleNode("/LiveCode/Chat/Message") != null)
                {
                    this.ProcessChatMessage(xmlDoc);
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.SlowProcessRecievedXML";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        private void ProcessTextChanged(XmlDocument xmlTextChanged)
        {
            try
            {
                //Make sure this is a text changed document
                if (xmlTextChanged.SelectSingleNode("/LiveCode/Text") != null)
                {
                    try
                    {
                        //Make sure all of these attributes exist, otherwise it's invalid and dismissed
                        int iSeq = int.Parse(xmlTextChanged.SelectSingleNode("/LiveCode/Text/@seq").Value);
                        int iUserID = int.Parse(xmlTextChanged.SelectSingleNode("/LiveCode/Text/@UserID").Value);
                        string sRelativePath = xmlTextChanged.SelectSingleNode("/LiveCode/Text/@RelativePath").Value;
                        int iStartLine = int.Parse(xmlTextChanged.SelectSingleNode("/LiveCode/Text/@iStartLine").Value);
                        int iStartIndex = int.Parse(xmlTextChanged.SelectSingleNode("/LiveCode/Text/@iStartIndex").Value);
                        int iNewEndLine = int.Parse(xmlTextChanged.SelectSingleNode("/LiveCode/Text/@iNewEndLine").Value);
                        int iNewEndIndex = int.Parse(xmlTextChanged.SelectSingleNode("/LiveCode/Text/@iNewEndIndex").Value);
                        int iOldEndLine = int.Parse(xmlTextChanged.SelectSingleNode("/LiveCode/Text/@iOldEndLine").Value);
                        int iOldEndIndex = int.Parse(xmlTextChanged.SelectSingleNode("/LiveCode/Text/@iOldEndIndex").Value);
                        if (xmlTextChanged.SelectSingleNode("/LiveCode/Text/@Text") != null)
                        {
                            string sText = xmlTextChanged.SelectSingleNode("/LiveCode/Text/@Text").Value;
                        }

                        //Send the document to each connected user
                        foreach (User user in this.clientUser.ConnectedProject.OnlineUsers)
                        {
                            if (user != this.clientUser)
                            {
                                user.Client.SendQuickData(xmlTextChanged);
                            }
                        }
                    }
                    catch (XmlException xmlEx)
                    {
                        //Ignore xml exceptions, this means it's an invalid document do you don't want to do anything with it
                        string message = "Method: Client.ProcessTextChanged";
                        if (this.clientUser != null)
                        {
                            message += "\nUser: " + this.clientUser.ID.ToString();
                        }
                        message += "\nException: " + xmlEx.Message;

                        this.clientHandler.AddMessage(message, System.Drawing.Color.Yellow);
                    }
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.ProcessTextChanged";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        private void ProcessChatMessage(XmlDocument xmlChat)
        {
            //Make sure this is a correct document
            if (xmlChat.SelectSingleNode("/LiveCode/Chat/Message") != null)
            {
                //Get the message info
                int iToID = int.Parse(xmlChat.SelectSingleNode("/LiveCode/Chat/Message/@toID").Value);
                int iFromID = int.Parse(xmlChat.SelectSingleNode("/LiveCode/Chat/Message/@fromID").Value);
                string sText = xmlChat.SelectSingleNode("/LiveCode/Chat/Message/@text").Value;

                this.clientHandler.SendChatMessage(iToID, iFromID, sText);
            }
        }
        #endregion

        #region Login
        private void ClientLogin(XmlDocument xmlDoc)
        {
            try
            {
                XmlDocument xmlLoginConfirm = new XmlDocument();
                XmlNode xmlLiveCode = xmlLoginConfirm.AppendChild(xmlLoginConfirm.CreateElement("LiveCode"));
                #region Login
                XmlNode xmlLogin = xmlLiveCode.AppendChild(xmlLoginConfirm.CreateElement("Login"));
                #endregion

                //Double check that this a valid login document
                if (xmlDoc.SelectSingleNode("/LiveCode/Login") != null && xmlDoc.SelectSingleNode("/LiveCode/Login/Username") != null && xmlDoc.SelectSingleNode("/LiveCode/Login/Password") != null)
                {
                    string sUsername = xmlDoc.SelectSingleNode("/LiveCode/Login/Username").InnerText;
                    string sPassword = xmlDoc.SelectSingleNode("/LiveCode/Login/Password").InnerText;

                    //Check the username and password in database
                    this.clientUser = this.VerifyClientLogin(sUsername, sPassword);

                    if (this.clientUser != null)
                    {
                        #region Add user info to xml
                        XmlNode xmlUserInfo = xmlLogin.AppendChild(xmlLoginConfirm.CreateElement("User"));
                        XmlNode xmlUserID = xmlUserInfo.AppendChild(xmlLoginConfirm.CreateElement("UserID"));
                        xmlUserID.InnerText = this.clientUser.ID.ToString();
                        XmlNode xmlProjects = xmlUserInfo.AppendChild(xmlLoginConfirm.CreateElement("Projects"));

                        XmlDocument xmlProjectDoc = new XmlDocument();
                        xmlProjectDoc.Load(ClientHandler.S_PROJECTS_FILE_PATH);

                        foreach (int iProjectID in this.clientUser.ProjectIDs)
                        {
                            XmlNode xmlProject = xmlProjects.AppendChild(xmlLoginConfirm.CreateElement("Project"));
                            XmlNode xmlProjectID = xmlProject.AppendChild(xmlLoginConfirm.CreateElement("ID"));
                            xmlProjectID.InnerText = iProjectID.ToString();

                            XmlNode xmlProjectName = xmlProject.AppendChild(xmlLoginConfirm.CreateElement("Name"));
                            if (xmlProjectDoc.SelectSingleNode("/LiveCodeProjects/Project[@id='" + iProjectID.ToString() + "']/@name") != null)
                            {
                                xmlProjectName.InnerText = xmlProjectDoc.SelectSingleNode("/LiveCodeProjects/Project[@id='" + iProjectID.ToString() + "']/@name").Value;
                            }
                            else
                            {
                                xmlProjectName.InnerText = "";
                            }
                        }
                        #endregion

                        #region Create XML with ports
                        List<int> lstQuickSendPorts = this.clientHandler.GetNewQuickSendPorts();
                        List<int> lstQuickRecievePorts = this.clientHandler.GetNewQuickRecievePorts();
                        List<int> lstSlowSendPorts = this.clientHandler.GetNewSlowSendPorts();
                        List<int> lstSlowRecievePorts = this.clientHandler.GetNewSlowRecievePorts();

                        #region Ports
                        XmlNode xmlPortsNode = xmlLiveCode.AppendChild(xmlLoginConfirm.CreateElement("Ports"));
                        XmlNode xmlSlowSendPorts = xmlPortsNode.AppendChild(xmlLoginConfirm.CreateElement("SlowSend"));
                        foreach (int port in lstSlowSendPorts)
                        {
                            XmlNode xmlSlowSendPort = xmlSlowSendPorts.AppendChild(xmlLoginConfirm.CreateElement("PortNum"));
                            xmlSlowSendPort.InnerText = port.ToString();
                        }
                        XmlNode xmlSlowRecievePorts = xmlPortsNode.AppendChild(xmlLoginConfirm.CreateElement("SlowRecieve"));
                        foreach (int port in lstSlowRecievePorts)
                        {
                            XmlNode xmlSlowRecievePort = xmlSlowRecievePorts.AppendChild(xmlLoginConfirm.CreateElement("PortNum"));
                            xmlSlowRecievePort.InnerText = port.ToString();
                        }

                        XmlNode xmlQuickSendPorts = xmlPortsNode.AppendChild(xmlLoginConfirm.CreateElement("QuickSend"));
                        foreach (int port in lstQuickSendPorts)
                        {
                            XmlNode xmlQuickSendPort = xmlQuickSendPorts.AppendChild(xmlLoginConfirm.CreateElement("PortNum"));
                            xmlQuickSendPort.InnerText = port.ToString();
                        }
                        XmlNode xmlQuickRecievePorts = xmlPortsNode.AppendChild(xmlLoginConfirm.CreateElement("QuickRecieve"));
                        foreach (int port in lstQuickRecievePorts)
                        {
                            XmlNode xmlQuickRecievePort1 = xmlQuickRecievePorts.AppendChild(xmlLoginConfirm.CreateElement("PortNum"));
                            xmlQuickRecievePort1.InnerText = port.ToString();
                        }
                        #endregion
                        #endregion

                        //Start listening on ports recieving data from before port data is sent to client
                        //Create buffers for incoming data
                        for (int i = 0; i < lstQuickSendPorts.Count; i++)
                        {
                            this.lstQuickRecieveDataBuffers.Add(new byte[256]);
                        }

                        for (int i = 0; i < lstSlowSendPorts.Count; i++)
                        {
                            this.lstSlowRecieveDataBuffers.Add(new byte[4096]);
                        }

                        this.lstQuickSendPorts = lstQuickRecievePorts;
                        this.lstSlowSendPorts = lstSlowRecievePorts;

                        //Listen for incoming connections on ports sent to client
                        this.StartQuickPortsListening(lstQuickSendPorts);
                        this.StartSlowPortsListening(lstSlowSendPorts);
                    }
                }

                this.SendData(xmlLoginConfirm);
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.ClientLogin";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        /// <summary>
        /// Checks the database for the username/password combination
        /// </summary>
        /// <param name="sUsername"></param>
        /// <param name="sPassword"></param>
        /// <returns>User if valid, null otherwise</returns>
        private User VerifyClientLogin(string sUsername, string sPassword)
        {
            try
            {
                XmlDocument xmlUsers = new XmlDocument();
                xmlUsers.Load(ClientHandler.S_USERS_FILE_PATH);

                //Make sure this is a valid users document
                if (xmlUsers.SelectNodes("/LiveCodeUsers/User").Count > 0)
                {
                    string sUserXpath = "/LiveCodeUsers/User[Name='" + sUsername + "' and Password='" + sPassword + "']";

                    //Check if username/password is correct
                    if (xmlUsers.SelectSingleNode(sUserXpath) != null)
                    {
                        User newUser = new User(xmlUsers.SelectSingleNode(sUserXpath), this);
                        return newUser;
                    }
                    else
                    {
                        sUserXpath = "";
                        return null;
                    }
                }
                else
                {
                    throw new XmlException("User xml document was invalid or contained no users");
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.VerifyClientLogin";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }

            return null;
        }

        private void ProjectLogin(XmlDocument xmlDoc)
        {
            try
            {
                //Make sure this is a project login document
                if (xmlDoc.SelectSingleNode("/LiveCode/ProjectLogin") != null)
                {
                    //Get the requested project id
                    int iProjectID = int.Parse(xmlDoc.SelectSingleNode("/LiveCode/ProjectLogin/ProjectID").InnerText);

                    XmlDocument xmlProjectLoginResponse = new XmlDocument();
                    XmlNode xmlLiveCode = xmlProjectLoginResponse.AppendChild(xmlProjectLoginResponse.CreateElement("LiveCode"));
                    XmlNode xmlProject = xmlLiveCode.AppendChild(xmlProjectLoginResponse.CreateElement("Project"));
                    XmlAttribute xmlProjectID = xmlProject.Attributes.Append(xmlProjectLoginResponse.CreateAttribute("id"));
                    xmlProjectID.Value = iProjectID.ToString();

                    //Check that the user id sent and the user id of the client are the same
                    int iUserID = int.Parse(xmlDoc.SelectSingleNode("/LiveCode/ProjectLogin/UserID").InnerText);
                    if (iUserID == this.clientUser.ID)
                    {
                        XmlDocument xmlProjectDoc = new XmlDocument();
                        xmlProjectDoc.Load(ClientHandler.S_PROJECTS_FILE_PATH);

                        //Check that the requested project has the selected user id within it
                        string sProjectXPath = "/LiveCodeProjects/Project[@id='" + iProjectID.ToString() + "']/User[@id='" + iUserID.ToString() + "']";
                        if (xmlProjectDoc.SelectSingleNode(sProjectXPath) != null)
                        {
                            //At this point the user is effectively logged into the project
                            this.clientHandler.ProjectLogin(iUserID, iProjectID);

                            //Check to see if the project has already been created for another user
                            Project project = this.clientHandler.GetProject(iProjectID);
                            if (project != null)
                            {
                                #region Project object already exists
                                this.clientUser.ConnectedProject = project;

                                //Get the client project location from xml
                                string sClientLocation = xmlProjectDoc.SelectSingleNode(sProjectXPath + "/ClientLocation").InnerText;

                                project.AddUser(this.clientUser, sClientLocation);

                                sProjectXPath = "/LiveCodeProjects/Project[@id='" + iProjectID.ToString() + "' and User[@id='" + iUserID.ToString() + "']]";
                                string sProjectName = xmlProjectDoc.SelectSingleNode(sProjectXPath + "/@name").Value;

                                XmlAttribute xmlProjectName = xmlProject.Attributes.Append(xmlProjectLoginResponse.CreateAttribute("name"));
                                xmlProjectName.Value = sProjectName;

                                XmlNode xmlClientLocation = xmlProject.AppendChild(xmlProjectLoginResponse.CreateElement("ClientLocation"));
                                xmlClientLocation.InnerText = project.UserClientPaths[this.clientUser];

                                XmlNode xmlRelativePaths = xmlProject.AppendChild(xmlProjectLoginResponse.CreateElement("RelativePaths"));
                                foreach (string sRelativeFilePath in project.RelativeFilePaths)
                                {
                                    XmlNode xmlFilePath = xmlRelativePaths.AppendChild(xmlProjectLoginResponse.CreateElement("FilePath"));
                                    xmlFilePath.InnerText = sRelativeFilePath;
                                }
                                #endregion
                            }
                            else
                            {
                                #region Project object does not exist
                                sProjectXPath = "/LiveCodeProjects/Project[@id='" + iProjectID.ToString() + "' and User[@id='" + iUserID.ToString() + "']]";

                                string sProjectName = xmlProjectDoc.SelectSingleNode(sProjectXPath + "/@name").Value;

                                XmlAttribute xmlProjectName = xmlProject.Attributes.Append(xmlProjectLoginResponse.CreateAttribute("name"));
                                xmlProjectName.Value = sProjectName;

                                XmlNode xmlProjectNode = xmlProjectDoc.SelectSingleNode(sProjectXPath);

                                project = new Project(iProjectID, sProjectName, xmlProjectNode, this.clientUser);
                                this.clientUser.ConnectedProject = project;

                                XmlNode xmlClientLocation = xmlProject.AppendChild(xmlProjectLoginResponse.CreateElement("ClientLocation"));
                                xmlClientLocation.InnerText = project.UserClientPaths[this.clientUser];

                                XmlNode xmlRelativePaths = xmlProject.AppendChild(xmlProjectLoginResponse.CreateElement("RelativePaths"));
                                foreach (string sRelativeFilePath in project.RelativeFilePaths)
                                {
                                    XmlNode xmlFilePath = xmlRelativePaths.AppendChild(xmlProjectLoginResponse.CreateElement("FilePath"));
                                    xmlFilePath.InnerText = sRelativeFilePath;
                                }
                                #endregion
                            }

                            this.clientHandler.ChatLogin(iUserID, iProjectID);
                        }
                    }

                    this.SendSlowData(xmlProjectLoginResponse);
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.ProjectLogin";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }
        #endregion

        #region Start Listening
        private void StartQuickPortsListening(List<int> lstQuickRecievePorts)
        {
            try
            {
                for (int i = 0; i < lstQuickRecievePorts.Count; i++)
                {
                    int port = lstQuickRecievePorts[i];

                    IPEndPoint ipLocal = (IPEndPoint)this.socDefaultClientConnection.LocalEndPoint;
                    ipLocal.Port = port;

                    Socket socNewListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
                    socNewListener.Bind(ipLocal);

                    this.lstQuickRecieveSockets.Add(socNewListener);

                    SocketInfo socketInfo = new SocketInfo(socNewListener, SocketInfo.SocketTypes.QuickRecieve, i);

                    try
                    {
                        //Listen for incomming connections
                        socNewListener.Listen(1);
                        //Create the callback for any client connections
                        socNewListener.BeginAccept(new AsyncCallback(OnClientConnect), socketInfo);
                    }
                    catch (SocketException socExp)
                    {
                        //TODO handle better

                        string message = "Method: Client.StartQuickPortsListening";
                        if (this.clientUser != null)
                        {
                            message += "\nUser: " + this.clientUser.ID.ToString();
                        }
                        message += "\nException: " + socExp.Message;

                        this.clientHandler.AddMessage(message, System.Drawing.Color.Red); ;
                    }
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.StartQuickPortsListening";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        private void StartSlowPortsListening(List<int> lstSlowRecievePorts)
        {
            try
            {
                for (int i = 0; i < lstSlowRecievePorts.Count; i++)
                {
                    int port = lstSlowRecievePorts[i];

                    IPEndPoint ipLocal = (IPEndPoint)this.socDefaultClientConnection.LocalEndPoint;
                    ipLocal.Port = port;

                    Socket socNewListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
                    socNewListener.Bind(ipLocal);

                    this.lstSlowRecieveSockets.Add(socNewListener);

                    SocketInfo socketInfo = new SocketInfo(socNewListener, SocketInfo.SocketTypes.SlowRecieve, i);

                    try
                    {
                        //Listen for incomming connections
                        socNewListener.Listen(1);
                        //Create the callback for any client connections
                        socNewListener.BeginAccept(new AsyncCallback(OnClientConnect), socketInfo);
                    }
                    catch (SocketException socExp)
                    {
                        //TODO handle better

                        string message = "Method: Client.StartSlowPortsListening";
                        if (this.clientUser != null)
                        {
                            message += "\nUser: " + this.clientUser.ID.ToString();
                        }
                        message += "\nException: " + socExp.Message;

                        this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
                    }
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.StartSlowPortsListening";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }
        #endregion

        #region Connect to client
        private void ConnectToClientQuickSend()
        {
            try
            {
                //TODO Disconnect from all sockets currently in the list of sockets


                //For each quick send port
                foreach (int port in this.lstQuickSendPorts)
                {
                    //Get the ipEndPoint with the current port
                    IPEndPoint ipClient = (IPEndPoint)this.socDefaultClientConnection.RemoteEndPoint;
                    ipClient.Port = port;

                    //Create a new client socket
                    Socket socNewClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);

                    //Add the new socket to the list of sockets
                    this.lstQuickSendSockets.Add(socNewClient);

                    try
                    {
                        //Connect to the client
                        socNewClient.Connect(ipClient);
                    }
                    catch (SocketException socExp)
                    {
                        //TODO handle better

                        string message = "Method: Client.ConnectToClientQuickSend";
                        if (this.clientUser != null)
                        {
                            message += "\nUser: " + this.clientUser.ID.ToString();
                        }
                        message += "\nException: " + socExp.Message;

                        this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
                    }
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.ConnectToClientQuickSend";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        private void ConnectToClientSlowSend()
        {
            try
            {
                //TODO Disconnect from all sockets currently in the list of sockets

                //For each quick send port
                foreach (int port in this.lstSlowSendPorts)
                {
                    //Get the ipEndPoint with the current port
                    IPEndPoint ipClient = (IPEndPoint)this.socDefaultClientConnection.RemoteEndPoint;
                    ipClient.Port = port;

                    //Create a new client socket
                    Socket socNewClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);

                    //Add the new socket to the list of sockets
                    this.lstSlowSendSockets.Add(socNewClient);

                    try
                    {
                        //Connect to the client
                        socNewClient.Connect(ipClient);
                    }
                    catch (SocketException socExp)
                    {
                        //TODO handle better

                        string message = "Method: Client.ConnectToClientSlowSend";
                        if (this.clientUser != null)
                        {
                            message += "\nUser: " + this.clientUser.ID.ToString();
                        }
                        message += "\nException: " + socExp.Message;

                        this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
                    }
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.ConnectToClientSlowSend";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }
        #endregion

        #region Events
        private void OnClientConnect(IAsyncResult asyn)
        {
            try
            {
                SocketInfo socketInfo = (SocketInfo)asyn.AsyncState;
                Socket socServer = socketInfo.ConnectionSocket.EndAccept(asyn);
                socketInfo.ConnectionSocket = socServer;

                if (socketInfo.Type == SocketInfo.SocketTypes.QuickRecieve)
                {
                    this.WaitForQuickData(socketInfo);
                }
                else
                {
                    this.WaitForSlowData(socketInfo);
                }
            }
            catch (SocketException socExp)
            {
                //TODO handle better

                string message = "Method: Client.OnClientConnect";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + socExp.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.OnClientConnect";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        private void OnQuickDataRecieved(IAsyncResult asyn)
        {
            try
            {
                SocketInfo socketInfo = (SocketInfo)asyn.AsyncState;

                //End recieve
                int iRx = 0;
                iRx = socketInfo.ConnectionSocket.EndReceive(asyn);

                //TODO Needs to put in thread
                #region Needs to split off into separate thread
                char[] chars = new char[iRx + 1];
                System.Text.Decoder decoder = System.Text.Encoding.UTF8.GetDecoder();
                int charLen = decoder.GetChars(bytDefaultDataBuffer, 0, iRx, chars, 0);
                String sData = new String(chars);

                if (sData != "\0")
                {
                    //Create xml document from string recieved
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.LoadXml(sData);

                    //Process xml document
                    this.QuickProcessRecievedXML(xmlDoc);

                    //Start waiting for data again
                    this.WaitForQuickData(socketInfo);
                }
                #endregion
            }
            catch (SocketException socEx)
            {
                //Client disconnection
                if (socEx.ErrorCode == 10054)
                {
                    if (this.bUserLoggedOn)
                    {
                        this.bUserLoggedOn = false;
                        this.clientHandler.UserLoggedOut(this.clientUser);
                    }
                }
                else
                {
                    //TODO handle better

                    string message = "Method: Client.OnQuickDataRecieved";
                    if (this.clientUser != null)
                    {
                        message += "\nUser: " + this.clientUser.ID.ToString();
                    }
                    message += "\nException: " + socEx.Message;

                    this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.OnQuickDataRecieved";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }

        private void OnSlowDataRecieved(IAsyncResult asyn)
        {
            try
            {
                SocketInfo socketInfo = (SocketInfo)asyn.AsyncState;

                //End recieve
                int iRx = 0;
                iRx = socketInfo.ConnectionSocket.EndReceive(asyn);

                char[] chars = new char[iRx + 1];
                System.Text.Decoder decoder = System.Text.Encoding.UTF8.GetDecoder();
                int charLen = decoder.GetChars(bytDefaultDataBuffer, 0, iRx, chars, 0);
                String sData = new String(chars);

                if (sData != "\0")
                {
                    XmlDocument xmlDoc = new XmlDocument();
                    try
                    {
                        //Create xml document from string recieved
                        xmlDoc.LoadXml(sData);
                    }
                    catch (XmlException xmlEx)
                    {
                        System.Windows.Forms.MessageBox.Show("Error creating xml document");
                        throw xmlEx;
                    }

                    //TODO make in new thread
                    //Process xml document
                    this.SlowProcessRecievedXML(xmlDoc);

                    //Start waiting for data again
                    this.WaitForSlowData(socketInfo);
                }
            }
            catch (SocketException socEx)
            {
                //Client disconnection
                if (socEx.ErrorCode == 10054)
                {
                    if (this.bUserLoggedOn)
                    {
                        this.bUserLoggedOn = false;
                        this.clientHandler.UserLoggedOut(this.clientUser);
                    }
                }
                else
                {
                    //TODO handle better

                    string message = "Method: Client.OnSlowDataRecieved";
                    if (this.clientUser != null)
                    {
                        message += "\nUser: " + this.clientUser.ID.ToString();
                    }
                    message += "\nException: " + socEx.Message;

                    this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
                }
            }
            catch (Exception e)
            {
                //TODO handle better

                string message = "Method: Client.OnSlowDataRecieved";
                if (this.clientUser != null)
                {
                    message += "\nUser: " + this.clientUser.ID.ToString();
                }
                message += "\nException: " + e.Message;

                this.clientHandler.AddMessage(message, System.Drawing.Color.Red);
            }
        }
        #endregion
    }
}
